// Copyright (c) 2010 god6or@gmail.com under MIT license.

package ul.ownet

import java.io.{File}
import java.util.{Date,Calendar}
import java.text.{DateFormat,SimpleDateFormat,NumberFormat}

import java.awt.{Dimension, Color, Font, event}
import javax.swing.{
    DefaultListModel,ListSelectionModel,SwingConstants,SwingUtilities,
    JSpinner,SpinnerNumberModel,JOptionPane,
    filechooser,border
}

import collection.mutable.{ListBuffer,ArrayBuffer}
import swing.{
    Swing,Orientation,Action,
    Component,Panel,BoxPanel,BorderPanel,FlowPanel,TabbedPane,
    Label,Button,ComboBox,CheckBox,TextField,ListView,ScrollBar,
    FileChooser
}
import swing.event._
import actors.{Actor}

import info.monitorenter.gui.chart.{Chart2D, ZoomableChart, ITrace2D, IAxis, traces, labelformatters, rangepolicies}
import info.monitorenter.util.{Range => AxisRange}

import ul.iobj.{IAttr,IObj,IObjT}
import ul.gettext.{GetText}
import ul.comm.{Serial, OneWire, OneWireSerial, OneWireThermometer, OneWire18B20}



class ViewPanel( parentTabs: TabbedPane, canPoll: Boolean = false, dbFile: String = null ) extends TabbedPane.Page("", new Label("")) {
    val thisPage = this
    val tr: (String)=>String = Config.tran.tr _

    title = tr("Poll")
    tip = tr("Poll page")

    // create DB for this page
    val db = new DB;
    def loadCSV( fn: String ) = { // Load CSV shortcut
        println("Loading CSV file: " + fn)
        val f = new File(fn)
        title = f.getName
        tip = f.getCanonicalPath
        db.clear
        db.fromCSV( fn )
        chartUpdate( true )
    }
    def saveCSV( fn: String ) = { // Save CSV shortcut
        println("Saving CSV file: " + fn)
        db.toCSV( fn )
    }

    // chart
    val plotColors = List( // color of traces
        Color.RED, Color.GREEN, Color.BLUE,
        Color.ORANGE, Color.YELLOW, Color.CYAN,
        Color.PINK, Color.LIGHT_GRAY,
        Color.MAGENTA, Color.BLACK
    )
    var (xMin, xMax, xWidth, xRangeMin, xRangeMax, xRangeWidth) = (0.0, 999.0, 0.0, 0.0, 999.0, 0.0)
    val chartScrollBar = new ScrollBar {
        orientation = Orientation.Horizontal
        minimum = 0
        maximum = 999
        value = 0
        visibleAmount = 1000
        
        object al extends event.AdjustmentListener {
            def adjustmentValueChanged(e: event.AdjustmentEvent) = {
                val value = e.getValue
                val newXRangeMin = (value * xWidth) / 1000.0 + xMin
                chartPanel.zoom( newXRangeMin, newXRangeMin + xRangeWidth )
            }
        }
        peer.addAdjustmentListener( al )
    }
    val chartPanel: ZoomableChart = new ZoomableChart { //with event.KeyListener {
        val xAxis = getAxisX
        xAxis.setAxisTitle( new IAxis.AxisTitle(tr("time")) )
        xAxis.setFormatter( new labelformatters.LabelFormatterDate( DateFormat.getDateTimeInstance.asInstanceOf[SimpleDateFormat] )) //new DateTimeLabelFormatter )
        xAxis.setMajorTickSpacing( 1.0 )
        xAxis.setPaintGrid( true )
        xAxis.setPaintScale( true )
        //println("Range policy: " + xAxis.getRangePolicy)

        val yAxis = getAxisY
        yAxis.setAxisTitle( new IAxis.AxisTitle(tr("temperature, °C")) )
        val nf = NumberFormat.getNumberInstance()
        nf.setMaximumIntegerDigits(3)
        nf.setMaximumFractionDigits(1)
        nf.setMinimumFractionDigits(1)
        yAxis.setFormatter( new labelformatters.LabelFormatterNumber(nf) )
        //getAxisY.setRange( new AxisRange( -20.0, 100.0 ))
        yAxis.setStartMajorTick( true )
        yAxis.setMajorTickSpacing( 1.0 )
        yAxis.setMinorTickSpacing( 0.1 )
        yAxis.setPaintGrid( true )
        yAxis.setPaintScale( true )
        setUseAntialiasing( true )
        setToolTipType( Chart2D.ToolTipType.DATAVALUES )
        
        override def propertyChange(evt: java.beans.PropertyChangeEvent) = {
            //println("Range policy: " + xAxis.getRangePolicy)
            //val xAxis = getAxisX()
            xMin = xAxis.getMinValue; xMax = xAxis.getMaxValue;
            xRangeMin = xAxis.getMin; xRangeMax = xAxis.getMax;
            xWidth = xMax - xMin
            xRangeWidth = xRangeMax - xRangeMin
            var (value, extent) = (0, 1000)
            if (xWidth > 0) {
                value = (((xRangeMin - xMin) * 1000.0) / xWidth).toInt
                extent = ((xRangeWidth * 1000.0) / xWidth).toInt
            }
            chartScrollBar.peer.setValues( value, extent, 0, 999 )
            chartScrollBar.repaint
            /*println("xMin: " + xMin + ", xMax: " + xMax +
                ", xRangeMin: " + xRangeMin + ", xRangeMax: " + xRangeMax +
                ", xWidth: " + xWidth + ", xRangeWidth: " + xRangeWidth +
                "value: " + value + ", extent: " + extent)
            */super.propertyChange( evt )
        }

    }

    var sensorsSerials: Seq[Long] = Nil
    var sensorsNames: Seq[String] = Nil
    var chartTraces: Seq[traces.ATrace2D] = Nil
    def chartUpdate( updateAll: Boolean = true ) = {

        if (updateAll) {
            // update all chart traces
            sensorsSerials = db.serials
            sensorsNames = db.names
            val nSensors = sensorsSerials.length
            var nTraces = chartPanel.getTraces().size
            //println("nSensors: " + nSensors + ", nTraces: " + nTraces)

            // add absent traces
            if (nTraces != nSensors) {
                chartPanel.removeAllTraces()
                chartTraces = (for (i <- 0 to nSensors-1) yield new traces.Trace2DSimple)
                for (trace <- chartTraces)
                    chartPanel.addTrace(trace, chartPanel.getAxisX, chartPanel.getAxisY );
            }
            chartPanel.zoomAll

            // fill traces data
            var traceCnt = 0
            val traceIterator = chartPanel.iterator
            var (timeMin, timeMax, tMin, tMax) = (
                java.lang.Double.MAX_VALUE, java.lang.Double.MIN_VALUE,
                java.lang.Double.MAX_VALUE, java.lang.Double.MIN_VALUE
            )
            
            sensorsPanel.contents.clear
            sensorsPanel.contents ++= (for (i <- 0 to nSensors-1) yield
                new CheckBox {
                    val sensorIndex = i
                    selected = true
                    action = new Action("") {
                        override def apply = {
                            chartTraces(sensorIndex).setVisible(selected)
                        }
                    }
                }
            )
            sensorsPanel.repaint
            while (traceIterator.hasNext) {
                val trace = traceIterator.next
                val serial = sensorsSerials(traceCnt)
                val name = sensorsNames(traceCnt)
                
                trace.removeAllPoints()
                trace.setName( name )
                trace.setColor( plotColors( traceCnt ))
                
                val sensorCheckBox = sensorsPanel.contents(traceCnt).asInstanceOf[CheckBox]
                sensorCheckBox.action.title = name
                sensorCheckBox.foreground = plotColors(traceCnt)

                // fill points data
                for (row <- db.rows if row.serial == serial)
                    trace.addPoint( row.time, row.t );

                traceCnt += 1
            }
        } else {
            //println("Updating chart from " + db.lastSize + " to " + db.size)
            var traceCnt = 0
            val traceIterator = chartPanel.iterator
            while (traceIterator.hasNext) {
                val trace = traceIterator.next
                val serial = sensorsSerials(traceCnt)
                var sensorTemp = 0.0
                for (rowCnt <- db.lastSize to db.size-1 if db.rows(rowCnt).serial == serial) {
                    trace.addPoint( db.rows(rowCnt).time, db.rows(rowCnt).t );
                    sensorTemp = db.rows(rowCnt).t
                }
                val sensorCheckBox = sensorsPanel.contents(traceCnt).asInstanceOf[CheckBox]
                sensorCheckBox.action.title = sensorsNames(traceCnt) + ": " + format("%3.2f", sensorTemp)

                traceCnt += 1
            }
        }
        
        // repaint chart
        chartPanel.setRequestedRepaint( true )
    }
    object sensorsReadCompleteListener extends Actor {
        start
        var lastAutoSaveTime = -1L
        def act { loop { react {
                    case Sensors.SensorsReadComplete( updateDB ) =>
                        if (updateDB) {
                            // add new record to DB
                            if (db.newStart) db.newStart = false
                            else {
                                //println("DB size before add: " + db.size + ", lastSize: " + db.lastSize)
                                db.lastSize = db.size;
                                for (sensor <- Sensors.sensors) db.add( sensor );
                                //println("DB size after add: " + db.size)
                                if (db.lastSize != db.size) {
                                    if (db.lastSize == 0) chartUpdate(true)
                                    else chartUpdate(false)
                                }
                                
                                // autosave
                                val curAutoSaveTime = System.currentTimeMillis
                                if (lastAutoSaveTime != -1) {
                                    //println("Autosave delta: " + Config.dbAutoSavePeriod + ", current: " + (curAutoSaveTime - lastAutoSaveTime))
                                    if ((curAutoSaveTime - lastAutoSaveTime) >= Config.dbAutoSavePeriod) {
                                        saveCSV( Config.dbFile )
                                        lastAutoSaveTime = curAutoSaveTime
                                    }
                                } else lastAutoSaveTime = curAutoSaveTime
                            }
                        }
                    case _ =>
        }}}
    }
    if (canPoll) Sensors.SensorsReader ! Sensors.AddReadCompleteListener(sensorsReadCompleteListener)
    


    var pollStarted = false
    val pollStartButton: Button = new Button { action = new Action( tr("start") ) {
        toolTip = tr("start sensor polling")
        xLayoutAlignment = 0.0F
        override def apply() = {
            if (db.size > 0) {
                if (JOptionPane.showConfirmDialog(parentTabs.peer, tr("Save current data ?"),
                    tr("Confirm data save"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
                    pollSave();
            }
            SwingUtilities.invokeLater(
                new Thread {
                    override def run {
                        //parentTabs.enabled = false
                        // disable configuration page
                        parentTabs.pages(0).enabled = false

                        pollStartButton.action.enabled = false
                        pollStopButton.action.enabled = true
                        pollSaveButton.action.enabled = false
                        pollLoadButton.action.enabled = false
                    }
                }
            )
            db.clear
            pollStarted = true
            Sensors.SensorsReader ! Sensors.SensorsPollStart( Config("pollPeriod").i )
        }
    }}
    val pollStopButton: Button = new Button { action = new Action( tr("stop") ) {
        enabled = false
        toolTip = tr("stop sensor polling")
        xLayoutAlignment = 0.0F
        override def apply() = {
            SwingUtilities.invokeLater(
                new Thread {
                    override def run {
                        parentTabs.pages(0).enabled = true
                        pollStartButton.action.enabled = true
                        pollStopButton.action.enabled = false
                        pollSaveButton.action.enabled = true
                        pollLoadButton.action.enabled = true
                    }
                }
            )
            Sensors.SensorsReader ! Sensors.SensorsPollStop
            pollStarted = false
        }
    }}


    // save polled data
    val pollSaveDialog = new FileChooser {
        title = tr("Save to CSV file")
        fileSelectionMode = FileChooser.SelectionMode.FilesOnly
        multiSelectionEnabled = false
    }
    def pollSave( fn: String = null ) = {
        var fName: File = null
        if (fn == null) {
            val calendar = Calendar.getInstance
            //calendar.setTimeInMillis( System.currentTimeMillis() )
            pollSaveDialog.selectedFile = new File(
                Config("pollLastLocation").s + File.separator + "temp_" +
                    format( "%02d%02d%02d_%02d%02d%02d",
                        calendar.get(Calendar.YEAR) % 100,
                        calendar.get(Calendar.MONTH) + 1,
                        calendar.get(Calendar.DATE) + 1,
                        calendar.get(Calendar.HOUR_OF_DAY),
                        calendar.get(Calendar.MINUTE),
                        calendar.get(Calendar.SECOND)
                    ) + ".csv"
            )
            if (pollSaveDialog.showSaveDialog( parentTabs ) == FileChooser.Result.Approve) fName = pollSaveDialog.selectedFile
        } else fName = new File(fn);

        if (fName != null) {
            db.toCSV( fName.getCanonicalPath )
            if ((fn == null)&&(fName != null)) Config("pollLastLocation").s = fName.getParentFile.getCanonicalPath;
        }
    }
    val pollSaveButton: Button = new Button { action = new Action( tr("save") ) {
        toolTip = tr("save temperature data")
        xLayoutAlignment = 0.0F
        override def apply() = {
            pollSave();
        }
    }}
    
    val pollLoadButton: Button = new Button { action = new Action( tr("load") ) {
        toolTip = tr("load temperature data")
        xLayoutAlignment = 0.0F
        val pollLoadDialog = new FileChooser {
            title = tr("Load from CSV file")
            fileSelectionMode = FileChooser.SelectionMode.FilesOnly
            multiSelectionEnabled = false
        }
        override def apply() = {
            pollLoadDialog.selectedFile = new File( Config("pollLastLocation").s + File.separator + "*.csv" )
            if (pollLoadDialog.showOpenDialog( parentTabs ) == FileChooser.Result.Approve) {
                if (pollLoadDialog.selectedFile.exists) {
                    Config("pollLastLocation").s = pollLoadDialog.selectedFile.getParentFile.getCanonicalPath
                    db.clear
                    db.fromCSV( pollLoadDialog.selectedFile.getCanonicalPath )
                    chartUpdate()
                }
            }
        }
    }}
    
    val sensorsPanel = new FlowPanel(FlowPanel.Alignment.Left)(new Label) {
        xLayoutAlignment = 0.0F
        yLayoutAlignment = 0.0F
        maximumSize = new Dimension( java.lang.Integer.MAX_VALUE, 250 )
        peer.setBorder( new javax.swing.border.EtchedBorder )
    }
    
    content = new BoxPanel(Orientation.Vertical) {
        contents ++= List(
            Swing.VStrut(5)
            ,new BoxPanel(Orientation.Horizontal) {
                xLayoutAlignment = 0.0F
                yLayoutAlignment = 0.0F
                peer.setBorder( new javax.swing.border.EtchedBorder )
                if (canPoll) contents ++= List( pollStartButton, pollStopButton, pollSaveButton )
                contents ++= List(
                    
//                    ,pollLoadButton
                    //,Swing.HStrut(5)
                    new Button { action = new Action( tr("show all") ) {
                        toolTip = tr("show all plot")
                        override def apply() = {
                            chartPanel.zoomAll
                            chartPanel.getAxisX.setRangePolicy(new rangepolicies.RangePolicyUnbounded)
                        }
                    }}
                    ,new Button { action = new Action( tr("fit Y") ) {
                        toolTip = tr("fit Y axis")
                        override def apply() = {
                            val (xMin, xMax) = (chartPanel.getAxisX.getMin, chartPanel.getAxisX.getMax)
                            chartPanel.zoomAll
                            chartPanel.zoom( xMin, xMax )
                        }
                    }}
                    ,Swing.HGlue
                )
            }
            ,Swing.VStrut(5)
            ,sensorsPanel
        )
        peer.add( chartPanel )
        contents += {
            new BoxPanel(Orientation.Horizontal) {
                contents += chartScrollBar
            }
        }
        
    }

    // load DB file if given
    if (dbFile != null) loadCSV( dbFile );
}

